// ==UserScript==
// @name         IT天空-定时每日签到
// @namespace    https://bbs.tampermonkey.net.cn/
// @version      0.1.0
// @description  IT天空社区用户签到定时每日自动签到脚本
// @author       Free-wang
// @contributor  DeepSeek 提供部分代码、逻辑分析、以及语法设计的辅助
// @license      MIT
// @supportURL   
// @website      https://gitee.com/ihacker/UserScripts/
// @source       https://gitee.com/ihacker/UserScripts/blob/master/crontab-user-script/IT%E5%A4%A9%E7%A9%BA-%E5%AE%9A%E6%97%B6%E6%AF%8F%E6%97%A5%E7%AD%BE%E5%88%B0.js
// @icon         https://www.itsk.com/favicon.ico
// @crontab      * * once * *
// @grant        GM_xmlhttpRequest
// @grant        GM_notification
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_info
// @grant        GM_log
// @connect      itsk.com
// @connect      api.itsk.com
// @require      https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js
// ==/UserScript==

/* ==UserConfig==
SignConfig:
  configA:
    title: IT天空登录帐号
    description: 请输入用户名或手机号
    type: text
    default: 
  configB:
    title: IT天空登录密码
    description: 请输入您的密码
    type: text
    default: 
    min: 6
    max: 32
    password: true
 ==/UserConfig== */

/**
 * 辅助函数
 * @description 生成随机Nonce（Base64URL编码，22字符）
 */
function generateNonce() {
    const array = new Uint8Array(16);
    crypto.getRandomValues(array);
    return btoa(String.fromCharCode.apply(null, array))
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=+$/, '');
}

/**
 * 辅助函数
 * @description 生成当前Unix时间戳（秒）
 */
function generateTimestamp() {
    return Math.floor(Date.now() / 1000);
}

/**
 * 辅助函数
 * @description 生成签名（SHA-1哈希）
 * @param {number} timestamp - Unix时间戳（秒）
 * @param {string} nonce - 随机字符串
 * @returns {string} 签名（SHA-1哈希）
 */
async function generateSignature(timestamp, nonce) {
    const input = `${timestamp}${nonce}`;
    const encoder = new TextEncoder();
    const data = encoder.encode(input);
    const hashBuffer = await crypto.subtle.digest('SHA-1', data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

/**
 * 获取通用请求头
 * @returns {Object} 通用请求头
 */
function getCommonHeaders() {
    return {
        'Accept': 'application/json, text/plain, */*',
        'Accept-Encoding': 'gzip, deflate, br',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        'Connection': 'keep-alive',
        'Host': 'api.itsk.com',
        'Origin': 'https://www.itsk.com',
        'Referer': 'https://www.itsk.com/',
        'Sec-Fetch-Dest': 'empty',
        'Sec-Fetch-Mode': 'cors',
        'Sec-Fetch-Site': 'same-site',
        'User-Agent': navigator.userAgent,
        'accept': 'application/json',
        'content-type': 'application/json',
        'sec-ch-ua': '"Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"'
    };
}

/**
 * 请求方法封装
 * @typedef {Object} GMRequestOptions
 * @property {string} method - 请求方法
 * @property {string} url - 请求URL
 * @property {Object} headers - 请求头
 * @property {string} data - 请求体
 */
function sendRequest(options) {
    return new Promise((resolve, reject) => {
        try {
            GM_xmlhttpRequest({
                ...options, onload: resolve, onerror: reject
            });
        } catch (e) {
            GM_log(`《${GM_info.script.name}》脚本提示，发起请求失败：${e}`, "error");
            throw new Error(`发起请求失败：${e}`);
        }
    });
}

/**
 * 响应方法封装
 * @typedef {Object} GMResponse
 * @property {number} code - 响应码
 * @property {string} message - 响应消息
 * @property {Object} data - 响应数据
 */
function handleResponse(response, action) {
    try {
        const responseData = JSON.parse(response.responseText);
        if (responseData && responseData.code === 200) {
            return responseData;
        }
    } catch (e) {
        GM_log(`《${GM_info.script.name}》脚本提示，解析响应失败：${action}异常 — ${response} — ${e}`, "error");
        throw new Error(`解析响应失败：${action}异常 — ${response} — ${e}`);
    }
}


/**执行签到函数
 * @returns {Promise<any|boolean|string>} 返回签到结果
 */
async function executeSign() {
    // 获取配置
    const loginIdentifier = GM_getValue("SignConfig.configA");
    const password = GM_getValue("SignConfig.configB");

    if (!loginIdentifier || !password) {
        GM_notification({
            title: `《${GM_info.script.name}》脚本提示`,
            text: "请先前往脚本配置中填写IT天空的登录帐号和密码",
            requireInteraction: true
        });
        throw new Error("请先前往脚本配置中填写IT天空的登录帐号和密码");
    }

    // 生成登录请求的动态值
    const timestampLogin = generateTimestamp();
    const nonceLogin = generateNonce();
    const signatureLogin = generateSignature(timestampLogin, nonceLogin);

    // 构建登录请求头
    const loginHeaders = {
        ...getCommonHeaders(),
        'authorization': '',
        'x-request-nonce': nonceLogin,
        'x-request-signature': signatureLogin,
        'x-request-timestamp': timestampLogin.toString()
    };

    // 发起登录请求
    const loginResponse = await sendRequest({
        method: "POST", url: "https://api.itsk.com/webapi/username/login", headers: loginHeaders, data: JSON.stringify({
            "login_identifier": loginIdentifier, "password": password, "source": "pc"
        })
    });

    // 处理登录响应
    const loginData = handleResponse(loginResponse, "登录");
    // 获取登录后取到的Token
    const accessToken = loginData.data?.access_token;
    GM_log(`《${GM_info.script.name}》脚本提示，登录成功，获取到的access_token为：${accessToken}`, "info");

    // 生成签到请求的动态值
    const timestampSign = generateTimestamp();
    const nonceSign = generateNonce();
    const signatureSign = generateSignature(timestampSign, nonceSign);

    // 构建签到请求头
    const signHeaders = {
        ...getCommonHeaders(),
        'Authorization': accessToken,
        'x-request-nonce': nonceSign,
        'x-request-signature': signatureSign,
        'x-request-timestamp': timestampSign.toString()
    };

    // 发起签到请求
    const signResponse = await sendRequest({
        method: "GET", url: "https://api.itsk.com/webapi/sign", headers: signHeaders
    });

    // 处理签到响应
    const signData = handleResponse(signResponse, "签到");
    GM_log(`《${GM_info.script.name}》脚本提示，签到成功，响应返的response为：${signData}`, "info");

    // 解析中文消息并显示通知
    const message = signData.message.replace(/\\u(\w{4})/gi, (match, grp) => String.fromCharCode(parseInt(grp, 16)));
    GM_notification({
        title: `《${GM_info.script.name}》脚本提示`,
        text: `IT天空用户签到成功，返回信息：${message}`,
        requireInteraction: true
    });

    return true;
}

/**
 * 主函数
 * @returns {Promise<string>} 签到结果
 */
return new Promise((resolve, reject) => {
    executeSign()
        .then(resolve)
        .catch(error => {
            GM_log(`《${GM_info.script.name}》脚本提示，脚本执行失败：${error}`, "error");
            reject(error);
        });
});